// 创建子进程
const childProcess = require("node:child_process");
const os = require("node:os");

const curEnvPlatform = os.platform();

// child_process 遵从 Nodejs 的设计思想，提供了同步/异步的方式来实现

const { exec, execFile, spawn, fork, spawnSync } = childProcess;

{
  // exec(command[, options][, callback])
  // command<string>: 要运行的命令
  // options<object>: 一些配置项
  // - cwd<string> | <URL>: 子进程的当前工作目录, 默认值是 process.cwd()
  // - env<object>: 环境变量键值对, 默认值是 process.env
  // - encoding<string>: 字符编码, 默认值是 'utf8'
  // - shell<string>: 用于执行命令的 shell, 默认值是 '/bin/sh' on Unix, 'cmd.exe' on Windows
  // - signal<AbortSignal >: 允许使用 AbortSignal 中止子进程
  // - timeout <number> 默认值：0
  // - maxBuffer <number> 标准输出或标准错误上允许的最大数据量（以字节为单位）。如果超过，则子进程将终止并截断任何输出。请参阅 maxBuffer 和 Unicode 的警告。默认值：1024 * 1024。
  // - killSignal <string> | <integer> 默认值 ‘SIGTERM’
  // - uid <number> 设置进程的用户省份
  // - gid <number> 设置进程的群组身份
  // - windowsHide <boolean> 隐藏通常在 Windows 上创建的子进程控制台窗口 默认值：false
  // callback <Function> 当子进程终止时调用。回调函数有三个参数，error, stdout, stderr。

  // exec 是异步的，不会阻塞主进程。
  // exec 是通过 shell 来执行命令的，所以可以使用 shell 的语法。例如，可以使用管道、重定向等。
  // "dir /B D:\\private\\code\\nodejs\\classis_3",
  exec(`dir /b ${__dirname}`, (error, stdout, stderr) => {
    if (error) {
      throw error;
    }
    // console.log(stdout);
  });

  // 如果使用 util.promisify 来将 exec 转换为 promise，那么就可以使用 async/await 来处理异步操作
  // 31 行的另一种实现方式
  const { promisify } = require("node:util");
  const execPromise = promisify(exec);
  (async () => {
    const { stdout, stderr } = await execPromise(`dir /b ${__dirname}`);
    // console.log(stdout);
  })();

  // cwd
  exec("cd", { cwd: "D:\\private\\code\\nodejs" }, (error, stdout, stderr) => {
    // 打印出 D:\\private\\code\\nodejs
    // console.log(stdout);
  });

  // env
  exec(
    'node -e "console.log(process.env.NODE_ENV)"',
    { env: { NODE_ENV: "production" } },
    (error, stdout, stderr) => {
      // console.log(stdout);
    }
  );

  // encoding
  exec("cd", { encoding: "utf-8" }, (error, stdout, stderr) => {
    // 打印出 D:\\private\\code\\nodejs
    // console.log(stdout);
  });

  // exec 调用系统的 shell 来执行命令，可以通过 options.shell 来指定使用哪种 shell
  exec(
    "dir",
    {
      shell: "powershell.exe",
      encoding: "utf-8",
    },
    (error, stdout, stderr) => {
      // console.log(`stdout: ${stdout}`);
    }
  );

  // signal - 可以通过 AbortControoler 来中止命令的执行
  const controller = new AbortController();
  exec(
    `findstr AbortController ${__filename}`,
    { signal: controller.signal },
    (error) => {
      // console.error(error);
    }
  );
  controller.abort();

  // timeout 超时
  exec(
    "timeout /T 10",
    { timeout: 2000, shell: "cmd.exe" },
    (error, stdout, stderr) => {
      // console.log(error);
    }
  );

  // maxBuffer
  // chcp 65001 修改 cmd.exe 的编码格式为 utf-8（默认为GBK），避免乱码
  exec(
    `chcp 65001 | dir ${__filename}`,
    { maxBuffer: 10 },
    (error, stdout, stderr) => {
      // console.log(error);
    }
  );

  // 启动一个长时间运行的子进程
  // const child = exec('node -e "setInterval(() => {}, 1000)"', {
  //   killSignal: "SIGTERM",
  // });

  // 确保子进程启动
  // setTimeout(() => {
  //   // 发送终止信号
  //   child.kill("SIGTERM");
  // }, 500);

  // // 监听子进程的退出事件
  // child.on("exit", (code, signal) => {
  //   console.log("子进程退出了");
  // });
}

{
  // execFile(file[, args][, options][, callback])
  // file<string>: 要运行的可执行文件的名称或路径
  // args<Array>: 字符串参数的列表
  // options<object>: 一些配置项 (同 exec.options)
  // callback<Function>: 当子进程终止时调用。回调函数有三个参数，error, stdout, stderr。

  // execFile 和 exec 类似，但是 execFile 不会使用 shell 来执行命令，而是直接执行可执行文件作为新进程，所以比 exec 效率略高； 不支持 使用 shell 的特性，如管道、重定向等。
  execFile("node", ["-e", "console.log('hello')"], (error, stdout, stderr) => {
    // console.log(stdout);
  });
}

{
  // fork(modulePath[, args][, options])
  // modulePath<string>|<URL>: 要在子进程中运行的模块的路径
  // args<string[]>: 字符串参数的列表
  // options<object>: 一些配置项 (部分配置同 exec.options：cwd、env、signal、timeout、uid、gid、killSignal)
  // - detached<boolean>: 准备子进程独立于其父进程运行。具体行为取决于平台
  // - execPath<string>: 用于创建子进程的可执行文件的路径。默认值是 process.execPath
  // - execArgv<string[]>: 传递给子进程的字符串参数列表。默认值是 process.execArgv
  // - serialization: 指定用于在进程之间发送消息的序列化类型。可能的值为 'json' 和 'advanced', 默认值：’json'
  // silent: 如果为 true，则子进程的标准输入、标准输出和标准错误将通过管道传输到父进程，否则它们将从父进程继承, 默认值：'false'
  // stdio: 用于配置父进程和子进程之间建立的 IPC 通道的配置。默认值：'pipe'
  // child_process.fork() 不支持 child_process.spawn() 中可用的 shell 选项，如果设置将被忽略
  // const forkProcess = fork("./child.js", null, {
  //   silent: true,
  // });
  // forkProcess.on("message", (data) => {
  //   console.log(data);
  // });
  // forkProcess.send("我是主进程");
  // forkProcess.stdout.on("data", (data) => {
  //   console.log(data instanceof Buffer);
  //   console.log("data: ", data instanceof Buffer ? data.toString() : data);
  // });
}

{
  // spawn(command[, args][, options])
  // command<string>: 要运行的命令
  // args<Array>：字符串参数列表
  // options<Object>：同 fork，还多一个argv0 (显式设置发送给子进程的 argv[0] 的值。如果未指定，这将设置为 command)
  let cmd;
  if (curEnvPlatform === "win32") {
    // cmd.exe 是 windows 下的命令行解释器
    // /c 表示执行完命令后关闭 cmd.exe
    // dir 是 windows 下的列出当前目录下的文件和文件夹的命令
    // /B 表示以简洁的方式列出文件和文件夹
    // __dirname nodejs 全局变量：当前文件绝对路径
    const dir = spawn("cmd.exe", ["/c", "dir", "/B", __dirname]);
    cmd = dir;
  } else {
    // ls 是 unix/linux 上的命令，列出目录下的内容
    // -1 以长格式列出目录内容
    // -h 以人类可读的方式列出文件大小
    // __dirname
    const ls = spawn("ls", ["-1h", __dirname]);
    cmd = ls;
  }

  cmd.stdout.on("data", (data) => {
    // console.log(`stdout: ${data}`);
  });

  cmd.stderr.on("data", (data) => {
    // console.log(`stderr: ${data}`);
  });

  cmd.on("close", (code) => {
    // console.log(`child process exited with code ${code}`);
  });

  const task = spawn("tasklist");
  const find = spawn("findstr", ["Code.exe"]);
  task.stdout.on("data", (data) => {
    find.stdin.write(data); // 将 task 的输出作为 find 的输入
  });

  task.stderr.on("data", (data) => {
    console.log(`task err data: ${data}`);
  });

  task.on("close", (code) => {
    console.log(`task process exited with code ${code}`);
    find.stdin.end(); // 关闭 find 的输入
  });

  find.stdout.on("data", (data) => {
    // console.log(`find data: ${data}`);
  });

  // stido 设置为 inherit，则子进程的输入输出会直接使用父进程的，而不是通过管道传输
  // 下面示例为例，输出的数据会在执行此文件的终端中显示
  spawn("cmd.exe", ["/c", "dir", __dirname], {
    stdio: "inherit",
  });
}
